Skip to content

ck_tile: fix 2:4-sparse SWMMAC correctness on gfx1201/RDNA4 (3 bugs) + fail→pass repro - #3759

Open
The-Monk wants to merge 2 commits into
ROCm:developfrom
The-Monk:gfx1201-sparse-swmmac-fixes
Open

ck_tile: fix 2:4-sparse SWMMAC correctness on gfx1201/RDNA4 (3 bugs) + fail→pass repro#3759
The-Monk wants to merge 2 commits into
ROCm:developfrom
The-Monk:gfx1201-sparse-swmmac-fixes

Conversation

@The-Monk

Copy link
Copy Markdown

0. TL;DR for the maintainer (schung-amd)

Three correctness bugs in ck_tile's SPARSE MmaOpFamily path produce wrong results on gfx1201/RDNA4 (v_swmmac_*_iu4). Root-caused, fixed (334-line patch, two files), and reproduced with a standalone test that fails on current CK and passes with the fix. We found these while building a production-grade 2:4-sparse fp8/int4 GEMM for LLM inference on RDNA4 — §3-4 give that context, which doubles as validation that the fixed path works end-to-end at scale.

1. The three bugs (files: sparse_mma_pipeline.hpp, sparse_transforms.hpp)

Bug 1 — compress_a_impl writes phantom values on <2-nonzero 2:4 groups.
The fallback ADataType nonzero_elems[2] = {a_vec[i*4+2], a_vec[i*4+3]} seeds the compressed pair from fixed input positions rather than true zero. A 2:4 group with 0 or 1 real nonzeros then reconstructs with a phantom value from a wrong lane (observed as register aliasing / UB). Fix: default nonzero_elems to true {0,0} and fill only real survivors — 0/1/2-nonzero groups all reconstruct exactly.

Bug 2 — packed sub-byte (pk_int4_t) group-of-4 scan treats a byte as one element.
The compaction's "nonzero per group of 4" scan operates on bytes, but a pk_int4_t byte holds two 4-bit values; a byte is "nonzero" if either nibble is, so the group-of-4 assumption over-counts and writes out of bounds into nonzero_elems[2]/[3]. Fix: packed-sub-byte-aware nibble counting (guarded with a static_assert that the packed path currently implements only pk_int4_t).

Bug 3 — compress_a_impl emits the two per-group idx metadata fields in an order that mismatches what the hardware reads, requiring a SWAP + XOR-1 to correct.
Within CK's own pk_int4_t packing, the metadata CK writes has the two idx fields swapped relative to which compressed survivor they govern, plus a XOR-1, versus what v_swmmac_i32_iu4 consumes on gfx1201 — so the sparse result comes out wrong (pos(HIGH-nibble survivor, found first) = idx1_field XOR 1, pos(LOW-nibble survivor, found second) = idx0_field XOR 1). Dense iu4 was confirmed correct first (11 seeds) to isolate this to the sparse metadata path. Fix: emit the idx fields with the swap+XOR so the hardware reconstructs the correct positions (Idx0 < Idx1, the documented 2:4 contract, is naturally satisfied).
Precision note (so this isn't over-stated): we verified this is specific to CK's compress/packing convention, not a universal "the hardware swaps." An independently-derived encoder in our own driver produces correct metadata without the swap — and applying the swap to it breaks a working encoding. So the fix corrects CK's own ordering to match the hardware; it is not a claim about v_swmmac_iu4 behavior for all encoders.

Patch: gfx1201-sparse-fixes-b675945.patch (against b675945; survives rebase).

2. The test AMD asked for (fails on current CK, passes with the fix)

sparse_ck_warptile_probe.cpp drives CK's own machinery for the SPARSE MmaOpFamily — it builds Pipeline::AWarpDstrEncoding internally (no manual compression math), fills arbitrary-position 2:4 data (REAL_A_16x128, a genuine int8 2:4-pruned weight tile exported from a Quark sparse-llama checkpoint, density=0.494 exact2frac=0.977 — i.e. ~2.3% of 4-groups have <2 real nonzeros, which is exactly what triggers Bug 1's fallback), runs the SWMMAC GEMM (K=32/64/128, three FragsK tile sizes), and checks against a CPU int64-accumulate reference.

Re-run 2026-07-23, fresh scratch clone (~/ck-3753-scratch, exact commit b67594561, detached HEAD, untouched otherwise) — GPU0 only (HIP_VISIBLE_DEVICES=0, Radeon AI PRO R9700 gfx1201):

  • Unpatched ck_tile (commit b67594561 as-is): FAILmax_abs_err=352 on all three tile shapes (K32/K64/K128), deterministic/reproducible run-to-run (fixed real-weight A tile, seeded B):
    [K32_single_frag] M=16 N=16 K=32 -> max_abs_err=352 FAIL   (C_expected[0]=-813 C_actual[0]=-1165)
    [K64_2frag]       M=16 N=16 K=64 -> max_abs_err=352 FAIL   (C_expected[0]=-446 C_actual[0]=-798)
    [K128_4frag]      M=16 N=16 K=128 -> max_abs_err=352 FAIL  (C_expected[0]=429 C_actual[0]=77)
    === SOME FAILED ===
    
  • Patched (git apply gfx1201-sparse-fixes-b675945.patch on the same clone, same commit, recompiled, rerun): PASSmax_abs_err=0 on all three shapes, twice in a row:
    [K32_single_frag] M=16 N=16 K=32 -> max_abs_err=0 PASS
    [K64_2frag]       M=16 N=16 K=64 -> max_abs_err=0 PASS
    [K128_4frag]      M=16 N=16 K=128 -> max_abs_err=0 PASS
    === ALL PASS ===
    

Reproduction (from scratch, no dependency on our fork):

git clone https://github.com/ROCm/composable_kernel ~/ck-3753-scratch && cd ~/ck-3753-scratch && git checkout b675945
# unpatched build/run:
clang++ -x hip --offload-arch=gfx1201 -D__HIP_PLATFORM_AMD__=1 \
  -isystem <rocm-devel>/include -I ~/ck-3753-scratch/include \
  -std=c++17 -O3 -DUSE_REAL_TILE \
  -I <path-to>/sparse24-ck sparse_ck_warptile_probe.cpp -o probe_unpatched \
  -L <rocm-devel>/lib -lamdhip64
HIP_VISIBLE_DEVICES=0 ./probe_unpatched     # -> FAIL, max_abs_err=352
# patched build/run:
git apply gfx1201-sparse-fixes-b675945.patch
clang++ ... (same flags) -o probe_patched
HIP_VISIBLE_DEVICES=0 ./probe_patched       # -> PASS, max_abs_err=0

(We compiled with the ROCm-devel clang toolchain directly — clang++ -x hip --offload-arch=gfx1201; the distro hipcc on this box is a stale 5.7-era wrapper unrelated to CK's target toolchain and will not build ck_tile headers. Any current ROCm/HIP clang works the same way.)

ASAN attempted, not obtained (honest note, not a blocker): we tried to get a device-sanitizer stack trace on the Bug-1/Bug-2 OOB write for extra evidence (-fsanitize=address -fgpu-sanitize). Device ASAN needs an xnack+ target-ID variant; RDNA4 (gfx1201) does not accept an xnack+/xnack- feature suffix at all (clang++: error: invalid target ID 'gfx1201:xnack+') — HIP device-side ASAN is a gfx9/CDNA-class feature (gfx90a/gfx94x with xnack+), not available on RDNA4 consumer/workstation targets in this ROCm toolchain. We did not chase this further since it's not required to reproduce the bug — the max_abs_err numbers above are a complete, deterministic fail→pass.

We'll port this into CK's gtest format for the PR; the standalone repro is included so it's runnable without the full CK test build.

3. Context — why these bugs mattered (the "whole thing")

We hit these building a library-grade 2:4-sparse GEMM for RDNA4 LLM inference (llama.cpp/ggml fork, gfx1201, 2× R9700). The journey, honestly:

  • ISA ceiling is real: isolated v_swmmac microbench hits 765 TOP/s fp8-2:4 (2× dense) and 1531 TOP/s int4-2:4 (4×) at ILP≥4 — 88-100% of the R9700 spec.
  • A hand-written MMQ-grade 2:4 kernel (cooperative tiles, LDS staging, ILP≥4) reaches ~93% of our native-fp8 WMMA MMQ (which we built — none exists upstream) on the whole model, and is at or ahead of the library on the three dominant GEMM shapes (+1.8% / +3.9% / +32% on gate-up / q-o / down-proj).
  • The honest finding worth AMD seeing: the ISA 2×/4× sparsity ceiling does not translate to a compute-bound, well-tiled model GEMM — measured across three quality tiers, the sparsity factor is 2× (isolated ISA) → 1.25× (crude kernel) → ~1.01× (library-grade). Once the kernel is well-tiled, the GEMM is bound by the tiled system (LDS staging, occupancy), not the raw SWMMAC issue rate that sparsity accelerates. This is a real, reproducible microbench-vs-model boundary for RDNA4 structured sparsity.
  • The mechanism, made precise (int4-2:4): the "4×" decomposes as 2×(int4 vs int8) × 2×(2:4 sparsity). The first 2× is real and already captured by dense wmma_i32_16x16x32_iu4 — gfx1201's dense int4 engine is already K=32-wide. The second 2× washes: swmmac_i32_16x16x32_iu4 (749,761 GOP/s) ≈ wmma_i32_16x16x32_iu4 (765,061) = 0.98×, instruction-for-instruction, because dense int4 already occupies the K=32 slot. The only sparse tensor edge is the K=64 form (1.77×) — the same "2×-K" mechanism fp8-2:4 already showed washing at library grade. So on high-arithmetic-intensity LLM GEMM shapes (compute-bound), structured 2:4 is a ~1.2× weight-bandwidth win (memory-bound regime only), not the tensor 2×/4×. (Correctness first regardless — the perf question is separate, and now answered.)
  • The remaining gap to the library was pinned by direct profiling to two specific, non-magical things — a 2×-slow activation-quantize helper (LDS+sync vs shuffle) and one occupancy-starved small-N shape — both fixed [§4].

4. Closing the gap — result

The 8% was pinned (by direct rocprofv3 head-to-head) to two things, NOT the big GEMMs (we're at/ahead of our own fp8 MMQ there: +1.8% / +3.9% / +32% on gate-up / q-o / down-proj). Fixing them:

build dense-MMQ (vs our fp8 MMQ) 2:4-MMQ (vs our fp8 MMQ)
capstone baseline 3786 (0.924×) 3823 (0.933×)
+ shuffle-based quantize (kept) 3900 (0.952–0.978×) 3944 (0.962–0.989×)
  • Quantize helper rewrite (kept): replaced a 32-thread / LDS+__syncthreads() reduction with the upstream mmq.cuh quantize shape — 128 threads, float4 loads, warp-shuffle max-reduction, zero LDS/sync (halved it, 36µs→~18µs). Closed ~40% of the remaining gap; both kernels now high-90s% of our native-fp8 kernel (0.989× same-session).
  • The N=1024 k/v small-shape (honest negative): both candidate levers — LDS bank-conflict padding and coarser K-per-sync — were implemented, correctness-verified, and measured worse (padding −2.5%; coarser-K a monotonic regression as it forced dropping double-buffering into an occupancy/LDS cliff). Both reverted. The outlier remains open, wants a different lever (shape-adaptive smaller tile without touching sync granularity).

Net: the hand-written 2:4 kernel now reaches ~96–99% of our native-fp8 WMMA MMQ (which we built — none exists upstream), is ahead on the FLOP-dominant GEMMs, and carries the measured ~1% sparsity edge over a comparable dense kernel — a library-competitive RDNA4 2:4 GEMM on top of the correctness fixes.

5. A related RDNA4 iu4 quirk (same class, different instruction — FYI, not part of the fix)

The Bug-3 element-ordering quirk on swmmac_iu4 is not isolated: an independent kernel-authoring effort on this box hit the same class on the dense v_dot8_i32_iu4/sudot8 path — "dots mismatched element pairs." RDNA4's iu4 instruction family appears to carry undocumented element-ordering conventions in both the sparse (SWMMAC metadata) and dense (dot8 operand pairing) paths. Documenting these in the ISA/CK would save the next implementer the multi-day reverse-engineering we did. Happy to write up the dense one too if useful.

6. Method (for reproducibility)

All numbers: gfx1201 (R9700), ROCm 7.14, GPU-isolated, warm, medians ≥3. Correctness gated by execution (CPU-reference compare), never inspection. The full capability-optimizer method (measure → grade vs the published peak → drive the lever) is what surfaced both the bugs and the microbench-vs-model boundary.

…+ repro

Fixes three bugs in the sparse MmaOpFamily compress path that produce wrong
results on gfx1201 (v_swmmac_*_iu4): phantom fill on <2-nonzero 2:4 groups,
pk_int4_t group-of-4 byte miscount (OOB), and idx-metadata ordering mismatch.
Adds a standalone repro that fails on unpatched CK (max_abs_err=352) and
passes with the fix (max_abs_err=0). Refs ROCm#3753.
The-Monk pushed a commit to The-Monk/llama.cpp that referenced this pull request Aug 5, 2026
…dormant, Stage 27)

All new paths gated behind GGML_HIP_* env vars -> OFF by default. Zero change
to default dispatch. Backs the AMD PR ROCm/composable_kernel#3759 §3-4 journey.

WINS:
- mul_mat_2of4_fp8_mmq: MMQ-grade 2:4-sparse fp8 GEMM (cooperative tiles, LDS
  staging, ILP>=4). At/ahead of our native-fp8 WMMA MMQ on the 3 dominant GEMM
  shapes (+1.8% gate-up / +3.9% q-o / +32% down-proj).
- mul_mat_dense_fp8_mmq: dense-fp8 MMQ twin.
- shuffle-based activation-quantize in mmvq.cu (128-thread, float4 loads,
  warp-shuffle max-reduction, zero LDS/__syncthreads) -- replaces the 32-thread
  LDS+sync reducer; ~36us->18us, closed ~40% of the remaining 8% gap. Both
  kernels now high-90s% of native-fp8 (0.989x same-session).
- k/v adaptive tile in mul_mat_2of4_fp8.cu.

CORRECTIONS / MEASUREMENT CONTROLS (the honest half):
- mul_mat_dense_fp8_v3: dense-fp8 twin used as the sparsity-isolation control.
  Proves the ISA 2x/4x sparsity ceiling WASHES to ~1% at library grade (2x ISA
  -> 1.25x crude -> ~1.01x well-tiled). This is the finding sent to AMD.
- swmmac24_iu4_fixed: NEGATIVE test. Applying CK's idx swap+XOR-1 to our OWN
  correct native SWMMAC encoder BREAKS it (err 0 -> 385). Proves the CK bug is
  compress/packing-convention-specific, NOT universal hardware behavior --
  folded into PR #3759 Bug 3 precision note.
- int4_24_probe: int4-2:4 probe (FAILING, gated off). Source of the "4x"
  decomposition: 2x(int4 packing, already in dense wmma_i32_16x16x32_iu4) x
  2x(2:4 sparsity, washes) -> net ~1.2x weight-bandwidth only.
@doplxyz

doplxyz commented Aug 14, 2026

Copy link
Copy Markdown

Thanks for digging this out and writing it up in this much detail — this had been sitting without a
review for three weeks, and the analysis in the description made it possible to check the claims
rather than guess at them. I have a gfx1201 box, so I ran it.

Short version: bug 1's fix holds up — I reproduced a failure on the base tree and, for what this
test measures, traced it to that one line. But the committed test can't be built from the PR alone, it passes on
the unfixed tree in the configuration that is buildable, and it never reaches the iu4 path that
bugs 2 and 3 are about. Separately, the changed code is not restricted to gfx1201, and I think that's
the thing to sort out before merge.

This is not a formal approval: I can only speak for gfx1201 and the int8 path.

Environment. AMD Radeon RX 9070 XT, gfx1201, amdgcn-amd-amdhsa--gfx1201; Linux 6.14.0-37,
amdgpu 6.19.14.31400100; container
rocm/pytorch:rocm7.14_ubuntu24.04_py3.12_pytorch_release_2.12.0, AMD clang 23.0.0git
(ROCm/llvm-project 46fcb339fb61). Trees compared: base
8fc1ac24e9bd7b431663a15e2122ce02c2979d37 — which is git merge-base of this PR's head and
develop, and base..head is exactly the three files in this PR — versus head
ac24ac28d662acf279478545cec541d4dde00f31. Identical test source and compile flags on both sides,
separate clean trees and build directories, three fresh processes per variant, identical stdout on
every repeat.

Two environment-side notes so the commands reproduce, neither of which is a problem with this PR:
that container needs --rocm-device-lib-path=<sdk>/lib/llvm/amdgcn/bitcode, and it ships only
libamdhip64.so.7, so a libamdhip64.so symlink is needed for the link step.


1. The repro can't be built from the PR alone

sparse_swmmac_correctness_repro.cpp:34 has

#include "real_a_tile.h"  // REAL_A_16x128: Quark int8 2:4 weight tile

outside the #ifdef USE_REAL_TILE at line 264, and real_a_tile.h isn't one of the PR's three files.
From a clean checkout the translation unit doesn't compile either way, so the max_abs_err=352
figures can't be checked by a reviewer.

I generated a substitute tile to get something running: same int8_t[16][128] shape, at most two
non-zeros per group of four along K, covering all six two-survivor position pairs, all four
single-survivor positions and the zero-survivor case, with values derived from (row, group, position) so that a misplaced survivor is more likely to show up numerically rather than cancel.
This is my input, not yours — nothing below should be compared against 352.

2. base fails, head passes

variant K=32 K=64 K=128
base 8fc1ac2 max_abs_err=56 76 92 FAIL
head ac24ac2 0 0 0 PASS

The harness wraps hipMalloc / hipMemcpy / hipMemset / hipDeviceSynchronize / hipFree in
HIP_CHECK_ERROR, and none tripped, so the failing numbers are a real kernel result rather than a
silent launch failure.

3. The whole effect is bug 1

Two more variants — base plus only the nonzero_elems true-zero default, and head with only that
line reverted:

variant K=32 K=64 K=128
base + bug-1 line only 0 0 0 PASS
head − bug-1 line 56 76 92 FAIL

The head − bug-1 stdout is identical to base's, byte for byte (diff on the run logs). So for the
failure this test measures, on these two commits, with this input and these three shapes, that one
line is both necessary and sufficient. I'm not claiming more than that — this doesn't establish
correctness over all valid inputs, types or architectures. The reasoning in your comment matches what
I see.

4. The test never reaches iu4, so bugs 2 and 3 get no numerical coverage from it

I disassembled the code objects of the kernels that actually launch, rather than grepping the
executable. The three SparseGemmKernel symbols (WaveTileK = 32 / 64 / 128) contain 1, 2 and 4
SWMMAC instructions respectively, zero v_wmma, zero v_mfma — and all seven are
v_swmmac_i32_16x16x32_iu8. There is no iu4 instruction anywhere in the binary.

That follows from the instantiation: the test uses SparseMmaPipeline<int8_t, int8_t, int32_t, ...>,
and int8_t picks up the generic numeric_traits::PackedSize == 1, so the if constexpr(PackedSize == 1) branch is taken, the packed-nibble path of bug 2 is compiled out, the SWAP + XOR-1 transform of
bug 3 is never reached, and TotalCompressedElems * MmaOp::APackedSize evaluates unchanged.

So the description reasons about all three bugs from the iu4 side, but the committed test only
exercises iu8. I haven't verified bugs 2 or 3 either — building an iu4 oracle independently of the
transform under test (nibble order, sign extension, logical vs packed K) is its own job, and reusing
your transform as the oracle would be circular.

What I could check is the shape side, with a probe that instantiates
SparseMmaPipeline<pk_int4_t, pk_int4_t, int32_t, ...> directly and launches it:

base head
TotalCompressedElems, K=32 4 8
TotalCompressedElems, K=64 8 16
TotalUncompressedElems 8 / 16 unchanged
IdxNumWords 1 unchanged at these shapes

Both trees compile, both emit v_swmmac_i32_16x16x32_iu4 and v_swmmac_i32_16x16x64_iu4, and
hipDeviceSynchronize() returns success. That's consistent with your ISA reading — 8 index values
per lane for a K=32 iu4 tile — but it's a shape and codegen check, not a correctness one.

5. The buildable configuration passes on the unfixed tree — and the reason is the one you identified

Built without -DUSE_REAL_TILE, so the #else synthetic path runs. (The substitute header from
§1 is still needed even here, since the #include is unconditional — dropping the define alone does
not make the file compile.)

result
base 8fc1ac2, no USE_REAL_TILE ALL PASS
head ac24ac2, no USE_REAL_TILE ALL PASS

I expected that to be because the synthetic input has no under-filled groups, but that isn't it. I
replayed the exact host-side fill (mt19937(42), uniform_int_distribution(-8, 8), then
apply_sparse_pattern) and counted:

K groups 0 non-zeros 1 non-zero 2 non-zeros
32 128 0 16 112
64 256 2 26 228
128 512 2 55 455

So under-filled groups are plentiful — the distribution includes 0 — and it still passes on the buggy
code. The reason is exactly the precondition you call out in the comment: apply_sparse_pattern
always zeroes slots 1 and 3, so survivors only ever sit at slots 0 and 2, and the old
{a_vec[i*4+2], a_vec[i*4+3]} default therefore always seeds slot 1 from a guaranteed zero. My tile
breaks it because survivors also sit at positions 2 and 3.

The practical consequence: once the missing header is supplied in whatever form, the configuration
that does not define USE_REAL_TILE reports a green run on the bug. Worth committing an input (or
generating one in the test) that puts survivors at position 3, and making that the default.

6. The change isn't scoped to gfx1201, and bug 1's fix demonstrably reaches CDNA

This is my main question before merge, and I don't think it's answerable from gfx1201 alone.

Neither changed hunk has an architecture predicate; both branch on PackedSize / APackedSize.
More to the point, the transforms selector in sparse_transforms.hpp:381 is specialized purely on
the op family:

struct MmaTransformsDefaultSelector<MmaOp, CompilerTarget,
                                    std::enable_if_t<MmaOp::OpFamily == MmaOpFamily::SPARSE>>
{ using SelectedTransforms = MmaDefaultTransformsSparse<MmaOp::kCompressionRatio>; };

with no enable_if_target_family_gfx*, unlike the dense gfx9 / gfx11 / gfx12 selectors right
alongside it. So every SPARSE op on every target resolves to this compress_a_impl. And
sparse/mfma/sparse_gfx9.hpp defines SPARSE ops for GFX942 / GFX950 over fp16_t, bf16_t,
int8_t, fp8_t, bf8_t — none of which specialize PackedSize, so they all take the same
PackedSize == 1 branch that bug 1's fix changes.

I checked that rather than inferring it. A compile-only probe that instantiates
SparseMmaPipeline<int8_t, int8_t, int32_t, 16, 16, 64, ..., Gfx942Target> and asserts

static_assert(std::is_same_v<
    typename MmaTransformsDefaultSelector<MmaOp942, Gfx942Target>::SelectedTransforms,
    MmaDefaultTransformsSparse<MmaOp942::kCompressionRatio>>);

compiles on both trees at --offload-arch=gfx942, and the resulting code object contains
v_smfmac_i32_16x16x64_i8. So that CDNA instantiation does route through the compress_a_impl this
PR changes, and takes the PackedSize == 1 branch.

That makes bug 1's fix a behaviour change for gfx942 as well, for inputs whose survivors don't sit
where the old default assumed. I think it's the correct fix and my gfx1201 result supports it — but
whether CDNA results actually change in practice, and whether anything downstream depended on the old
behaviour, is a runtime question I can't answer without the hardware.

The narrower half of this: I found no pk_int4_t SPARSE op for gfx9 in the tree, so bug 2's packed
branch and bug 3's SWAP + XOR-1 look gfx12-only in practice today, which limits the blast radius of
the empirically-derived metadata mapping considerably. It's still worth saying out loud that those
branches carry no architecture condition, so a future CDNA pk_int4 sparse op would silently inherit a
mapping that was measured on gfx1201.

On CI: the GitHub API reports no check suites and no commit statuses for either cd34d2f or
ac24ac2. I can't tell from outside whether anything ran elsewhere, but from the PR there's no
visible regression signal for the architectures the selector reaches. That's a project-side
infrastructure question rather than something to put on you.

7. Minor, non-blocking

  • The comments carry internal process labels (Stage-17b fix (local, not upstreamed),
    Stage-17c CLOSURE fix, this Stage-17c audit's own probe) — worth rewording for upstream.
  • The new static_assert(PackedSize == 2 && is_same_v<LogicalADataType, pk_int4_t>) narrows the
    packed path to pk_int4_t explicitly. I haven't tested whether any other packed A type reaches
    this code today, so I don't know if it changes anything in practice — just noting it's a scope
    change beyond the three bugs.
  • You already note the gtest port is pending. For what it's worth, test/ck_tile/CMakeLists.txt
    enumerates its subdirectories with explicit add_subdirectory(...) calls rather than a glob, and
    gfx1201_sparse_swmmac isn't among them, so the port will need that line too.

What would let me say more

  1. Commit real_a_tile.h, or generate an equivalent tile inside the test, so the fail→pass is
    reproducible from the PR alone — and make sure the default-built configuration is one that fails
    before the fix.
  2. Add an iu4 / pk_int4_t case, since bugs 2 and 3 have no numerical coverage without one.
  3. Say what architecture scope is intended, given §6.

Happy to re-run any of this on gfx1201. If it would help, I can also try to pin bug 3's mapping
empirically and independently of sparse_transforms — a minimal wave-level kernel issuing raw
v_swmmac_*_iu4 with one-hot operands, enumerating the metadata encodings and reading the
contributing positions back out of the result — and then compare what that gives against SWAP + XOR-1.
That would be a second measurement rather than a derivation from the doc, which as you note doesn't
capture the iu4-specific detail. Say the word and I'll put the generator, flags, logs and per-symbol
disassembly somewhere you can pull them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants